# -*-coding=utf-8 -*-
"""
    @author:sirius
    @time:2017.5.14
"""
import sys
import cosine_dis
import numpy as np
import pandas as pd

from common.util import format_time
from common.recommend_result import show_result
from common.recommend_app import recommend_by_user_fc
from common.util import get_event_timestamp_list


# 算法流程：
# 1、建立应用-用户的倒排表，表示应用被那些用户安装
# 2、从目标用户安装的每一个应用开始遍历
# 3、寻找和目标用户有共同安装应用交集的所有用户作为初始邻居
# 4、对初始邻居里边的每一个邻居，计算和目标用户的余弦夹角相似度
# 5、建立相似度到用户的倒排表，相似度从高到低排列
# 6、根据相似度找出最近k邻居
# 7、从最近k邻安装的所有的应用开始遍历
#  这里的推荐思路是：对于最近k邻居安装的所有应用中的某一应用m
#  如果m仅仅被一个邻居安装，那么目标用户对该应用的的兴趣度就是目标用户和这个邻居的相似度
#  如果m被多个邻居安装，那么目标用户对此应用的兴趣度为目标用户与这些邻居相似度之和
# 8、建立目标用户兴趣度-应用id的倒排表
# 9、根据兴趣度由大到小进行推荐


# 读取文件，读取以行为单位，每一行是列表里的一个元素
def read_file(filename):
    csv_file = pd.read_csv(filename, sep=',')
    return csv_file


# 数据格式化为二维数组
def get_rating_info(ratings):
    rates = np.array(ratings.loc[:, :])
    return rates


# 生成用户评分数据结构
def get_user_score_data_structure(rates):
    # listUser2Score[2]=[(23,2,0),(43,0,1),(36,1,2)....] 表示用户2的应用安装列表（apps list）23,42,36（AppID）
    # 用户2应用23电量消耗层级为2(高),网路消耗层级为0(低);
    # 应用43电量消耗层级为0(低),网路消耗层级为1(中);
    # 应用36电量消耗层级为1(中),网路消耗层级为2(高);

    list_user2_score = {}
    # dictItem2Users{},key=item id,value=user id list
    dict_item2_users = {}
    for rate in rates:
          # app_events.csv
          # user id | app name | battery | network.
          user_rank = (rate[3], rate[2], rate[5], rate[6])
          score_tmp = ()
          if rate[3] in list_user2_score:
               for user2score in list_user2_score[rate[3]]:
                    if rate[2] in user2score:
                        score_tmp = user2score
                        user_rank = (rate[3], rate[2], user2score[2] + rate[5], user2score[3] + rate[6])
                        break

               if score_tmp in list_user2_score[rate[3]]:
                    list_user2_score[rate[3]].remove(score_tmp)
               list_user2_score[rate[3]].append(user_rank)
          else:
               list_user2_score[rate[3]] = [user_rank]

          if rate[2] in dict_item2_users:
               if rate[3] not in dict_item2_users[rate[2]]:
                    dict_item2_users[rate[2]].append(rate[3])
          else:
               dict_item2_users[rate[2]] = [rate[3]]

    return list_user2_score, dict_item2_users


# 计算与目标用户相似的k个用户
def get_nearest_neighbor(user_id, list_user2_score, dict_item2_users, k):
    # listUser2Score[2]=[(23,2,0),(43,0,1),(36,1,2)....]
    # 表示用户2的应用安装列表（apps list）23,42,36（AppID）
    # 用户2应用23电量消耗层级为2(高),网路消耗层级为0(低);
    # 应用43电量消耗层级为0(低),网路消耗层级为1(中);
    # 应用36电量消耗层级为1(中),网路消耗层级为2(高);
    neighbor_list = []
    # 对于目标用户userId的每一个周期律item

    if user_id not in list_user2_score.keys():
        print('无该目标用户')
        sys.exit()

    for item in list_user2_score[user_id]:
        # dictItem2Users{},key=item id,value=user id list
        # item[0]表示应用id,item[1]表示应用电量评分,item[2]表示应用网络评分
        # dictItem2Users[item[0]]=dictItem2Users[应用id]=value=安装该应用的用户列表
        # 从安装该应用的用户列表里，计算目标用户和这个列表里边所有用户的相似度
        for neighbor in dict_item2_users[item[1]]:
            # 如果这个邻居不是目标用户并且这个邻居还没有被加入邻居集就加进来
            if neighbor != user_id and neighbor not in neighbor_list:
                 neighbor_list.append(neighbor)

    neighbors_dist = []
    # 里边存储的是[相似度，邻居id]
    for neighbor in neighbor_list:
        # listUser2Score[2]=[(23,2,0),(43,0,1),(36,1,2)....] 表示用户2的应用安装列表（apps list）23,42,36（AppID）
        # 用户2应用23电量消耗层级为2(高),网路消耗层级为0(低);应用43电量消耗层级为0(低),网路消耗层级为1(中);应用36电量消耗层级为1(中),网路消耗层级为2(高);
        dist = cosine_dis.get_similarity(user_id, list_user2_score[user_id], list_user2_score[neighbor])
        neighbors_dist.append([dist, neighbor])
        # 按照相似度倒排，相似度从高到低
    neighbors_dist.sort(reverse=True)
    print("序号\t用户ID\t用户相似度\t")
    for i in range(0, k):
        print("%d\t%s\t%f\t" % (i + 1, neighbors_dist[i][1], neighbors_dist[i][0]))
    return neighbors_dist


# userFC计算用户相似度，输入：文件名,用户ID,邻居数量
def user_fc(filename, user_id, k=5):
    # 读取文件
    contents = read_file(filename)
    # 文件格式数据转化为二维数组
    rates = get_rating_info(contents)
    # 标准化数据模块
    # 格式化成字典数据
    list_user2_score, dict_item2_users = get_user_score_data_structure(rates)
    # 找出最相似的前五个邻居
    neighbors_top_k = get_nearest_neighbor(user_id, list_user2_score, dict_item2_users, k)[:k]

    return list_user2_score, dict_item2_users, neighbors_top_k


# 获取特征矩阵列表
def get_feature():
    # 读取用户相关矩阵数据
    file_matrix = open('../period/md_cache/matrix.txt', 'r')
    try:
        matrix = file_matrix.read()
    finally:
        file_matrix.close()

    # 将字符串转换为字典格式
    matrix = eval(matrix)

    return matrix


# 获取特征矩阵列表
def get_neighbors():
    # 读取用户相关矩阵数据
    neighbors_from_period = open('../period/md_cache/neighbors.txt', 'r')
    try:
          data_neighbors = neighbors_from_period.read()
    finally:
          neighbors_from_period.close()

    # 将字符串转换为字典格式
    data_neighbors = eval(data_neighbors)

    return data_neighbors


# 测试学习模型
def test_model():
    # 事件序列
    event_num = 0
    # 目标用户
    target_user = 5449.0
    # 命中次数
    hitNum = 0
    # 应用记录列表文件路径
    APP_SYS_PATH = '../datasets/md_cache/app_sys_tmp.csv'
    # 获取事件列表
    EventList = list(get_event_timestamp_list(APP_SYS_PATH, target_user))
    # 计算用户相似度，输入参数：1. 数据集文件 2. 目标用户ID 3. 邻居个数
    listUser2Score, dictItem2Users, neighbors = user_fc(APP_SYS_PATH, target_user, 5)

    for event in EventList:
        global date_dict
        date_dict = {}
        date_dict = format_time(date_dict, event[1])
        # 根据用户相似度进行推荐，输入参数：1. 用户-相似度列表 2. app-用户字典 3. 用户ID 4. 邻居列表 5. 事件序列
        list_recommend_app_id, user_app, items_app = recommend_by_user_fc(date_dict, APP_SYS_PATH, listUser2Score,
                                                                          dictItem2Users, target_user, neighbors)
        print("-----------------------相似用户-----------------------")
        show_result(APP_SYS_PATH, neighbors, list_recommend_app_id, items_app, date_dict)

        print("用户%s：%s" % (target_user, event[0]))
        if event[0] in [k[1] for k in list_recommend_app_id]:
            hitNum += 1
        event_num += 1
        print("-----------------------------------------------------")

    print("预测准确度：", float(hitNum) / event_num)


if __name__ == '__main__':
    test_model()
